import struct
import time
import datetime

import serial
import udsoncan
from crc import Calculator
import bincopy
import threading
import binascii
from udsoncan import MemoryLocation
from udsoncan import services
from sdk.cfg import tp_physaddr, tp_funcaddr, CRCconfig
import logging
from loguru import logger
import time


class PeriodThread(object):
    def __init__(self, period_func, args=None, kwargs=None):
        if kwargs is None:
            kwargs = {}
        if args is None:
            args = []
        self._thread = threading.Thread(target=self._run)
        self._function = period_func
        self._args = args
        self._kwargs = kwargs
        self._period = 0
        self._event = threading.Event()
        self._period_event = threading.Event()
        self._terminated = False

    def start(self):
        self._thread.start()

    def stop(self):
        self._terminated = True
        self._event.set()
        self._thread.join()

    def _run(self):
        while True:
            self._event.wait()
            self._event.clear()
            if self._terminated:
                break
            self._function(*self._args, **self._kwargs)
            while not self._period_event.wait(self._period):
                self._function(*self._args, **self._kwargs)
            self._period_event.clear()


def getDateTimeBytes():
    _year_high = int(str(datetime.datetime.now().year), 16) >> 8
    _year_low = int(str(datetime.datetime.now().year), 16) & 0xFF
    _month = int(str(datetime.datetime.now().month), 16)
    _day = int(str(datetime.datetime.now().day), 16)
    _hour = int(str(datetime.datetime.now().hour), 16)
    _minute = int(str(datetime.datetime.now().minute), 16)
    _second = int(str(datetime.datetime.now().second), 16)
    return _year_low, _month, _day, _hour, _minute, _second, 0x00, 0x00, 0x00


class FlashDownloader:
    def __init__(self, client=None, stack=None, MyLogger=None, progressbar=None, time=None):
        self.time = time
        self.t1 = 0
        self.t2 = 0
        self.step = 0
        self.Cnt = 0
        self._Threadflag = False
        self._Thread = None
        self.Application = None
        self.FlashDriver = None
        self.client = client
        self.stack = stack
        self.FBLFilePath = ''
        self.APPFilePath = ''
        self.segmentsdata = bytearray("", 'ASCII')
        self.FBLDataBatch = {}
        self.APPDataBatch = {}
        self.FBLCrcValues = 0
        self.APPCrcValues = 0
        self.calculator = Calculator(CRCconfig)
        self.logger = MyLogger
        self.progressbar = progressbar
        self.tp_funcaddr = tp_funcaddr
        self.tp_physaddr = tp_physaddr
        self.FlashCnt = 1

    def flash_change_address(self, phys, func):
        self.tp_physaddr = phys
        self.tp_funcaddr = func

    def flash_change_client(self, client, stack):
        self.client = client
        self.stack = stack

    def run(self):
        self.step = 0
        self.Cnt = 0
        self.FBLCrcValues = None
        self.APPCrcValues = None
        if (self.APPFilePath == '') & (self.FBLFilePath == ''):
            self.logger.warning('文件为空')
            self.stop()
        else:
            try:
                self.FlashDriver = bincopy.BinFile(self.FBLFilePath)
                self.Application = bincopy.BinFile(self.APPFilePath)
            except:
                self.logger.warning('文件无法读取,请确认刷写文件为s19或hex文件')
                self.stop()
            if (self.FlashDriver is not None) & (self.Application is not None):
                if self.client is None:
                    self.logger.warning("设备未打开,无法进行刷写")
                    self.stop()
                else:
                    while self.Cnt < self.FlashCnt:
                        self.Cnt += 1
                        self.progressbar['value'] = 0
                        self.logger.info('开始第' + str(self.Cnt) + '次刷写')
                        self.t1 = time.time()
                        self.logger.info('开始下载进程')
                        self.FBLDataBatch.clear()
                        self.APPDataBatch.clear()
                        if self.getSegmentsData():
                            self.step = self.step + 1
                            if self.prePrograming():
                                self.step = self.step + 1
                                if self.serverPrograming():
                                    self.step = self.step + 1
                                    self.postPrograming()
                                    self.step = self.step + 1
                        self.t2 = time.time()
                        self.logger.info(self.step)
                        if self.step == 4:
                            self.logger.info('下载用时' + str(round(self.t2 - self.t1, 2)) + '秒')
                            self.logger.info('结束第' + str(self.Cnt) + '次刷写进程')
                            self.Cnt = self.FlashCnt
                            self.stop()
                            self.logger.info('下载成功,退出下载进程')
                        else:
                            self.logger.warning('下载错误,请重试')

    def download(self):
        if not self._Threadflag:
            self._Thread = threading.Thread(target=self.run)
            self._Threadflag = True
            self._Thread.start()
        else:
            self.logger.info('程序正在下载,请勿重复')

    def stop(self):
        self.FBLDataBatch.clear()
        self.APPDataBatch.clear()
        self._Threadflag = False
        self.logger.info("step:" + str(self.step))
        self.step = 0
        try:
            self.client.ecu_reset(1)
        except:
            pass
        time.sleep(3)

    @logger.catch
    def getSegmentsData(self):
        try:
            self.FBLDataBatch.clear()
            self.APPDataBatch.clear()
            for segment in self.FlashDriver.segments:
                self.FBLDataBatch[MemoryLocation(address=segment.address, memorysize=int(
                    len(binascii.hexlify(segment.data).decode()) / 2), address_format=32,
                                                 memorysize_format=32)] = segment.data
            for segment in self.Application.segments:
                self.APPDataBatch[MemoryLocation(address=segment.address, memorysize=int(
                    len(binascii.hexlify(segment.data).decode()) / 2), address_format=32,
                                                 memorysize_format=32)] = segment.data
            self.logger.info('计算文件crc中.....')
            if (self.FBLCrcValues is None) | (self.APPCrcValues is None):
                for k in self.FBLDataBatch.keys():
                    self.FBLCrcValues = self.calculator.checksum(self.FBLDataBatch[k]).to_bytes(length=4,
                                                                                                byteorder="big")
                    self.logger.info(self.FBLCrcValues)
                    self.logger.info('计算FBL crc32= ' + hex(self.calculator.checksum(self.FBLDataBatch[k])))
                    self.progressbar.step(2)
                for k in self.APPDataBatch.keys():
                    self.APPCrcValues = self.calculator.checksum(self.APPDataBatch[k]).to_bytes(length=4,
                                                                                                byteorder="big")
                    self.logger.info(self.APPCrcValues)
                    self.logger.info('计算APP crc32= ' + hex(self.calculator.checksum(self.APPDataBatch[k])))
            # self.FBLCrcValues = b'+lR0'
            # self.APPCrcValues = b'\xff`\x02\xb5'
            self.progressbar.step(4)
            return True
        except:
            self.logger.warning("文件校验失败,请检查配置")
            self.stop()
            return False

    def prePrograming(self):
        try:
            self.logger.info("开始编程前准备")
            self.stack.set_address(self.tp_funcaddr)
            self.client.change_session(3)
            self.client.tester_present()
            self.stack.set_address(self.tp_physaddr)
            self.client.start_routine(routine_id=0x0200)  # 检查
            self.stack.set_address(self.tp_funcaddr)
            self.client.tester_present()
            self.client.control_dtc_setting(services.ControlDTCSetting.SettingType.off)
            self.stack.set_address(self.tp_funcaddr)
            self.progressbar.step(2)
            # try:
            #     # bootloader has no $28 service
            #     self.client.communication_control(0x01, 0x03)
            # except:
            #     self.logger.warning('bootloader has no $28 service')
            return True
        except:
            self.logger.warning('预编译条件错误')
            self.stop()
            return False

    def serverPrograming(self):
        self.logger.info("开始编程")
        try:
            self.stack.set_address(self.tp_physaddr)
            self.client.change_session(2)
            self.client.unlock_security_access(0x11)
            self.client.write_data_by_identifier(did=0xF012, value=getDateTimeBytes())
            self.progressbar.step(2)
            self.logger.info("下载FlashDriver")
            for k in self.FBLDataBatch.keys():
                _location = self.FBLDataBatch[k]
                resp = self.client.request_download(memory_location=k)
                _maxNumOfBlockLen = ((resp.data[1] << 8) | (resp.data[2] & 0xFF)) - 2
                _blockSequenceCounter = 1
                _progressbarCounter = 0
                _progressbarOnceCounter = int(len(_location) / _maxNumOfBlockLen / 4)
                while len(_location) > _maxNumOfBlockLen:
                    self.client.transfer_data(sequence_number=_blockSequenceCounter,
                                              data=bytes(_location[0:_maxNumOfBlockLen]))
                    _blockSequenceCounter += 1
                    _progressbarCounter += 1
                    if _progressbarCounter > _progressbarOnceCounter:
                        _progressbarCounter = 0
                        self.progressbar.step()
                    if _blockSequenceCounter > 0xFF:
                        _blockSequenceCounter = 0x00
                    _location = _location[_maxNumOfBlockLen:]
                self.client.transfer_data(sequence_number=_blockSequenceCounter, data=bytes(_location))
                self.client.request_transfer_exit()
                self.client.tester_present()
            self.client.start_routine(routine_id=0x0201, data=self.FBLCrcValues)
            for k in self.APPDataBatch.keys():
                self.logger.info("下载Application")
                self.client.start_routine(routine_id=0xFF00,
                                          data=struct.pack('BBBBBBBBB', 0x44, (k.address >> 24) & 0xFF,
                                                           (k.address >> 16) & 0xFF,
                                                           (k.address >> 8) & 0xFF, (k.address & 0xFF),
                                                           (k.memorysize >> 24) & 0xFF,
                                                           (k.memorysize >> 16) & 0xFF,
                                                           (k.memorysize >> 8) & 0xFF,
                                                           (k.memorysize & 0xFF)))
                _location = self.APPDataBatch[k]
                resp = self.client.request_download(memory_location=k)
                _maxNumOfBlockLen = ((resp.data[1] << 8) | (resp.data[2] & 0xFF)) - 2
                _blockSequenceCounter = 1
                _testerPresentCounter = 1
                _progressbarCounter = 0
                _progressbarOnceCounter = int(len(_location) / _maxNumOfBlockLen / 90)
                while len(_location) > _maxNumOfBlockLen:
                    self.client.transfer_data(sequence_number=_blockSequenceCounter,
                                              data=bytes(_location[0:_maxNumOfBlockLen]))
                    _blockSequenceCounter += 1
                    _testerPresentCounter += 1
                    _progressbarCounter += 1
                    if _progressbarCounter > _progressbarOnceCounter:
                        _progressbarCounter = 0
                        self.progressbar.step()
                    if _blockSequenceCounter > 0xFF:
                        _blockSequenceCounter = 0x00
                    _location = _location[_maxNumOfBlockLen:]
                    if _testerPresentCounter > 50:
                        self.client.tester_present()
                        _testerPresentCounter = 0
                self.client.transfer_data(sequence_number=_blockSequenceCounter, data=bytes(_location))
                self.client.request_transfer_exit()
                self.client.start_routine(routine_id=0x0201, data=self.APPCrcValues)
                self.client.start_routine(routine_id=0xFF01)
                return True
        except:
            self.logger.warning("刷写失败,请重试")
            self.stop()
            return False

    def flashing(self, k):
        _location = self.APPDataBatch[k]
        resp = self.client.request_download(memory_location=k)
        _maxNumOfBlockLen = ((resp.data[1] << 8) | (resp.data[2] & 0xFF)) - 2
        _blockSequenceCounter = 1
        _testerPresentCounter = 1
        _progressbarCounter = 0
        _progressbarOnceCounter = int(len(_location) / _maxNumOfBlockLen / 90)
        while len(_location) > _maxNumOfBlockLen:
            self.client.transfer_data(sequence_number=0x00,
                                      data=bytes(_location[0:_maxNumOfBlockLen]))
            _blockSequenceCounter += 1
            _testerPresentCounter += 1
            _progressbarCounter += 1
            if _progressbarCounter > _progressbarOnceCounter:
                _progressbarCounter = 0
                self.progressbar.step()
            if _blockSequenceCounter > 0xFF:
                _blockSequenceCounter = 0x00
            _location = _location[_maxNumOfBlockLen:]
            if _testerPresentCounter > 50:
                self.client.tester_present()
                _testerPresentCounter = 0
        self.client.transfer_data(sequence_number=0x00, data=bytes(_location))
        self.client.request_transfer_exit()

    def postPrograming(self):
        self.logger.info("开始编程后阶段")
        try:
            self.stack.set_address(self.tp_funcaddr)
            self.client.communication_control(0x0, 0x3)
            self.stack.set_address(self.tp_physaddr)
            self.client.change_session(1)
            self.logger.info("ECU复位成功")
            time.sleep(1)
            self.progressbar.step(3)
            self.stack.set_address(self.tp_funcaddr)
            self.client.change_session(3)
            self.client.control_dtc_setting(services.ControlDTCSetting.SettingType.on)
            self.client.change_session(1)
            self.logger.info('下载结束')
            self.progressbar['value'] = 100
            return True
        except:
            self.logger.warning('编程后阶段失败,请检查硬件连接')

    def getProgramingStatus(self):
        return self.step
